/*
 * Copyright (C) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "lottie_xcomponent_node.h"
#include "common/number_constant.h"
#include "node/lottie_manager.h"
#include "node/lottie_manager_map.h"
#include "task_pool/task_pool.h"
#include <arkui/native_interface.h>
#include <cstdint>
#include "deviceinfo.h"
#include "common/number_constant.h"
#include <native_window/graphic_error_code.h>
namespace LottieTurbo {

static std::map<std::string, OH_NativeXComponent_Callback *> &GetCallback()
{
    static std::map<std::string, OH_NativeXComponent_Callback *> callback;
    return callback;
}

static bool PushCallback(std::string id, OH_NativeXComponent_Callback *callback)
{
    if (callback == nullptr) {
        return false;
    }
    auto &map = GetCallback();
    auto it = map.find(id);
    if (it != map.end()) {
        map[id] = callback;
        return true;
    }
    return false;
}

static bool PopCallback(std::string id)
{
    auto &map = GetCallback();
    auto it = map.find(id);
    if (it != map.end()) {
        delete map[id];
        map[id] = nullptr;
        map.erase(id);
    }
    return true;
}

LottieXcomponentNode::LottieXcomponentNode(std::string xcomponentId, float radius, double bgColor)
{
    this->CreateXcomponentNode(xcomponentId, radius, bgColor);
}

LottieXcomponentNode::~LottieXcomponentNode()
{
    status_ = 1;
    arkUINativeNodeApi_->disposeNode(handle_);
}

ArkUI_NodeHandle LottieXcomponentNode::GetHandle() const
{
    return handle_;
}

void LottieXcomponentNode::CreateXcomponentNode(std::string xcomponentId, float radius, double bgColor)
{
    OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, arkUINativeNodeApi_);
    if (!arkUINativeNodeApi_) {
        DRAWING_LOGE("LottieXcomponentNode::CreateXcomponentNode lottieId(%{public}s): Error arkUINativeNodeApi_ NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    handle_ = arkUINativeNodeApi_->createNode(ARKUI_NODE_XCOMPONENT);
    if (!handle_) {
        DRAWING_LOGE("LottieXcomponentNode::CreateXcomponentNode lottieId(%{public}s): Error handle_ NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    if (xcomponentId.length() <= 0) {
        DRAWING_LOGE("LottieXcomponentNode::CreateXcomponentNode lottieId(%{public}s): Error xcomponentId NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    ArkUI_AttributeItem item = {nullptr, NUM_1, xcomponentId.c_str()};
    arkUINativeNodeApi_->setAttribute(handle_, NODE_XCOMPONENT_ID, &item);
    ArkUI_NumberValue backgroundColorValue[] = {{.u32 = static_cast<uint32_t>(bgColor)}};
    ArkUI_AttributeItem backgroundColorItem = {backgroundColorValue,
                                               sizeof(backgroundColorValue) / sizeof(ArkUI_NumberValue)};
    arkUINativeNodeApi_->setAttribute(handle_, NODE_BACKGROUND_COLOR, &backgroundColorItem);
    if (radius > 0) {
        DRAWING_LOGE("lal lottieId(%{public}f)", radius);
        ArkUI_NumberValue radiusValue[] = {{.f32 = radius}};
        ArkUI_AttributeItem radiusItem = {radiusValue, sizeof(radiusValue) / sizeof(ArkUI_NumberValue)};
        arkUINativeNodeApi_->setAttribute(handle_, NODE_BORDER_RADIUS, &radiusItem);
    }
    OH_NativeXComponent_Callback *callback = new OH_NativeXComponent_Callback;
    callback->OnSurfaceCreated = OnSurfaceCreated;
    callback->OnSurfaceChanged = OnSurfaceChanged;
    callback->DispatchTouchEvent = OnDispatchTouchEvent;
    callback->OnSurfaceDestroyed = OnSurfaceDestroyed;
    PushCallback(xcomponentId, callback);
    nativeXComponent_ = OH_NativeXComponent_GetNativeXComponent(handle_);
    if (!nativeXComponent_) {
        DRAWING_LOGE("LottieXcomponentNode::CreateXcomponentNode lottieId(%{public}s): Error GetNativeXComponent "
                     "nativeXComponent_ NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    OH_NativeXComponent_RegisterCallback(nativeXComponent_, callback);
    LottieManagerMap::getInstance().Add(xcomponentId);
    lottieRender_ = std::make_shared<LottieRender>();
}

void LottieXcomponentNode::SetAnimator()
{
    if (!handle_ || !lottieHandler_) {
        DRAWING_LOGE("LottieXcomponentNode::SetAnimator lottieId(%{public}s): Error Init params NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    ArkUI_ContextHandle contextHandle = OH_ArkUI_GetContextByNode(handle_);
    if (!contextHandle) {
        DRAWING_LOGE("LottieXcomponentNode::SetAnimator lottieId(%{public}s): Error contextHandle NULL",
                     lottieHandler_->GetLottieId().c_str());
        return;
    }
    lottieHandler_->SetAnimator(contextHandle);
}

void LottieXcomponentNode::SetHandler(std::shared_ptr<LottieHandler> lottieHandler)
{
    if (!handle_ || !lottieHandler) {
        DRAWING_LOGE("LottieXcomponentNode::SetHandle lottieId(%{public}s): Error Init params NULL",
                     !lottieHandler ? "" : lottieHandler->GetLottieId().c_str());
        return;
    }
    lottieHandler_ = lottieHandler;
}

void LottieXcomponentNode::Init()
{
    status_ = 0;
    int32_t width = 0;
    int32_t height = 0;
    if (!handle_ || !lottieHandler_) {
        DRAWING_LOGE("LottieXcomponentNode::Init lottieId(%{public}s): Error Init params NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    lottieHandler_->SetRenderCallBack([this]() { this->RenderCurrentFrame(); });
    lottieHandler_->SetAnimatorCallBack([this]() { this->RenderAndAdvanceFrame(); });
    lottieHandler_->SetContentModeUpdateCallback([this]() { this->OnUpdateContentMode(); });
    LottieManagerMap::getInstance().SetHandleById(lottieHandler_->GetLottieId(), lottieHandler_);
    LottieManagerMap::getInstance().GetRectById(lottieHandler_->GetLottieId(), width, height);
    if (width != 0 && height != 0) {
        lottieHandler_->SetLottieSize(width, height);
        if (lottieHandler_->updateContentMode) {
            lottieHandler_->updateContentMode();
        }
        if (lottieHandler_->GetLottieConfig()->GetAutoplay()) {
            lottieHandler_->Play();
        } else if (lottieHandler_->renderAndAdvanceFrame) {
            lottieHandler_->renderAndAdvanceFrame();
        }
    }
}

void LottieXcomponentNode::RenderCurrentFrame()
{
    if (!lottieHandler_ || !lottieHandler_->dataManager_ ||
        (!lottieHandler_->lottieListener_ && !lottieHandler_->clottieListener_)) {
        DRAWING_LOGE("LottieXcomponentNode::RenderCurrentFrame lottieId(%{public}s): Error OnDraw params NULL",
                     !lottieHandler_ ? "" : lottieHandler_->GetLottieId().c_str());
        return;
    }
    float absoluteFrameToPlay = static_cast<float>(lottieHandler_->GetAbsoluteFrameToPlay());
    if (!lottieHandler_->GetIsSubframeEnabled()) {
        absoluteFrameToPlay = std::round(absoluteFrameToPlay);
    }
    uint32_t lottieFileTotalFrame = static_cast<uint32_t>(lottieHandler_->GetLottieFileTotalFrames());
    DRAWING_LOGD(
        "LottieXcomponentNode:: Xid: %{public}s, absoluteFrameToPlay: %{public}f, lottieFileTotalFrame: %{public}d",
        lottieHandler_->GetLottieId().c_str(), absoluteFrameToPlay, lottieFileTotalFrame);
    int64_t current = lottieHandler_->currentIndex_++;
    // 绝对帧大于动画文件总帧数时不绘制
    if (absoluteFrameToPlay < lottieFileTotalFrame) {
        ScheduleRenderTask(absoluteFrameToPlay, current);
    }
}

/*
 * 这里添加丢帧逻辑，规则如下
 * 1. 当前需要渲染的帧和最新的一帧相差超过10帧，则开启丢帧模式
 * 2. 为了保证动画的连续性，丢帧时保证连续丢帧不超过N帧
 * 3. 通过总帧数 / 20 计算出来, N最大为3帧
 */
void LottieXcomponentNode::ScheduleRenderTask(float absoluteFrameToPlay, int64_t current)
{
    auto func = [this](std::shared_ptr<LottieHandler> handler, CalculatePosition position,
                       std::shared_ptr<LottieRender> render, float ab, int64_t current) {
        if (ShouldSkipFrame(handler, current)) {
            return;
        }

        if (handler->isDestroy_) {
            return;
        }

        if (handler->GetPlayState() == LottieHandler::PlayState::STOP ||
            handler->GetPlayState() == LottieHandler::PlayState::PAUSE) {
            handler->SetPlayState(LottieHandler::PlayState::END);
        }

        if (handler->dataManager_ == nullptr || handler->dataManager_->player == nullptr) {
            DRAWING_LOGE("LottieXcomponentNode::ScheduleRenderTask player is Null");
            render->RenderClear(handler->GetLottieId());
            return;
        }

        const LOTLayerNode *lotLayerNode = GenerateRenderTree(handler, position, render, ab);

        HITRACE("Lottie::render->Render",
                render->Render(handler->GetLottieId(), lotLayerNode, (LottieTurbo::CalculatePosition *)&position, ab););

        NotifyDrawFrameListeners(handler);
    };

    TaskPoolLocal::GetInstance().PushTask(func, lottieHandler_, position_, lottieRender_, absoluteFrameToPlay, current);
}

bool LottieXcomponentNode::ShouldSkipFrame(std::shared_ptr<LottieHandler> handler, int64_t current)
{
    auto loss = handler->GetTotalFrames() / handler->skipFrameBase;
    if (loss > handler->skipFrameLimits) {
        loss = handler->skipFrameLimits;
    }

    if (abs(current - handler->currentIndex_) >= handler->lossFrameLimits && handler->skip_ < loss) {
        handler->skip_++;
        return true;
    }

    handler->skip_ = 0;
    return false;
}

const LOTLayerNode *LottieXcomponentNode::GenerateRenderTree(std::shared_ptr<LottieHandler> handler,
                                                             const CalculatePosition &position,
                                                             std::shared_ptr<LottieRender> render,
                                                             float absoluteFrameToPlay)
{
    const LOTLayerNode *lotLayerNode = nullptr;
    HITRACE(
        "Lottie::player->renderTree",
        if (handler->GetLottieConfig()->GetImageCache() &&
            render->ImageCacheState(position.width, position.height, absoluteFrameToPlay)) {
            DRAWING_LOGD("LottieXcomponentNode:: skip RenderTree frame:%{public}f", absoluteFrameToPlay);
        } else {
            lotLayerNode = handler->dataManager_->player->renderTree(absoluteFrameToPlay, position.width,
                                                                     position.height, position.keepAspectRatio);
        });
    return lotLayerNode;
}

void LottieXcomponentNode::NotifyDrawFrameListeners(std::shared_ptr<LottieHandler> handler)
{
    if (handler->lottieListener_ != nullptr) {
        auto func = [](LottieListener *listener, LottieEventType type) {
            if (listener) {
                listener->TriggerEvent(type);
            }
        };
        auto trigger = new LottieHandler::TriggerData;
        trigger->func = std::bind(func, handler->lottieListener_.get(), LottieEventType::ON_DRAW_FRAME);
        napi_call_threadsafe_function(handler->safeFunc_, trigger, napi_tsfn_blocking);
    }

    if (handler->clottieListener_ && handler->clottieListener_->drawFrame) {
        handler->clottieListener_->drawFrame();
    }
}

void LottieXcomponentNode::RenderAndAdvanceFrame()
{
    if (firstFrame_) {
        firstFrame_ = false;
    } else if (lottieHandler_ && lottieHandler_->dataManager_) {
        if (lottieHandler_->dataManager_->GetVisible() == false) {
            lottieHandler_->ResetLastTimeStamp();
            return;
        }
    }
    if (status_) {
        return;
    }
    if (lottieHandler_->lottieListener_) {
        auto func = [](LottieListener *listener, LottieEventType type) {
            if (listener) {
                listener->TriggerEvent(type);
            }
        };
        auto trigger = new LottieHandler::TriggerData;
        trigger->func = std::bind(func, lottieHandler_->lottieListener_.get(), LottieEventType::ON_ENTER_FRAME);
        napi_call_threadsafe_function(lottieHandler_->safeFunc_, trigger, napi_tsfn_blocking);
    }
    if (lottieHandler_->clottieListener_ && lottieHandler_->clottieListener_->enterFrame) {
        lottieHandler_->clottieListener_->enterFrame();
    }
    if (lottieHandler_ && lottieHandler_->animator_ &&
        (lottieHandler_->animator_->GetStatus() == Animator::AnimatorStatus::PLAY ||
         lottieHandler_->animator_->GetStatus() == Animator::AnimatorStatus::INIT)) {
        this->RenderCurrentFrame();
        lottieHandler_->AdvanceFrame();
    }
}

void LottieXcomponentNode::OnUpdateContentMode()
{
    if (lottieHandler_ != nullptr) {
        UpdateContentMode();
    }
}

void LottieXcomponentNode::InitializePosition(int componentWidth, int componentHeight)
{
    position_.x = 0;
    position_.y = 0;
    position_.width = componentWidth;
    position_.height = componentHeight;
    position_.keepAspectRatio = true;
    position_.crop = false;
}

void LottieXcomponentNode::ApplyCoverMode(const ContentModeParams& params)
{
    if (params.animWidth == 0 || params.animHeight == 0) {
        DRAWING_LOGE("LottieXcomponentNode::ApplyCoverMode Number is 0");
        return;
    }
    bool useWidthScale = (params.animAspectRatio <= params.componentAspectRatio);
    float scale = useWidthScale ?
        static_cast<float>(params.componentWidth) / params.animWidth :
        static_cast<float>(params.componentHeight) / params.animHeight;
    
    if (useWidthScale) {
        position_.height = static_cast<int>(params.animHeight * scale);
        position_.y = -(position_.height - params.componentHeight + PIXEL_OFFSET) / CENTER_DIVISOR;
    } else {
        position_.width = static_cast<int>(params.animWidth * scale);
        position_.x = -(position_.width - params.componentWidth + PIXEL_OFFSET) / CENTER_DIVISOR;
    }
}

void LottieXcomponentNode::ApplyTopBottomMode(const std::string& contentMode, const ContentModeParams& params)
{
    if (params.animWidth == 0 || params.animHeight == 0) {
        DRAWING_LOGE("LottieXcomponentNode::ApplyTopBottomMode Number is 0");
        return;
    }
    bool useWidthScale = (params.animAspectRatio >= params.componentAspectRatio);
    float scale = useWidthScale ?
        static_cast<float>(params.componentWidth) / params.animWidth :
        static_cast<float>(params.componentHeight) / params.animHeight;
    
    if (useWidthScale) {
        position_.height = static_cast<int>(params.animHeight * scale);
        if (contentMode == "Bottom") {
            position_.y = params.componentHeight - position_.height;
        }
    } else {
        position_.width = static_cast<int>(params.animWidth * scale);
        if (contentMode == "Bottom") {
            position_.x = params.componentWidth - position_.width;
        }
    }
}

void LottieXcomponentNode::ApplyContainCropMode(const ContentModeParams& params)
{
    if (params.animWidth == 0 || params.animHeight == 0) {
        DRAWING_LOGE("LottieXcomponentNode::ApplyContainCropMode Number is 0");
        return;
    }
    bool useWidthScale = (params.animAspectRatio <= params.componentAspectRatio);
    float scale = !useWidthScale ? static_cast<float>(params.componentWidth) / params.animWidth
                                 : static_cast<float>(params.componentHeight) / params.animHeight;

    position_.height = static_cast<int>(params.animHeight * scale);
    position_.width = static_cast<int>(params.animWidth * scale);

    if (useWidthScale) {
        position_.x = -(position_.width - params.componentWidth + PIXEL_OFFSET) / CENTER_DIVISOR;
    } else {
        position_.y = -(position_.height - params.componentHeight + PIXEL_OFFSET) / CENTER_DIVISOR;
    }
    position_.crop = true;
}

void LottieXcomponentNode::UpdateContentMode()
{
    size_t animWidth;
    size_t animHeight;
    std::string contentMode = lottieHandler_->GetLottieConfig()->GetContentMode();
    lottieHandler_->dataManager_->GetSize(animWidth, animHeight);
    int componentWidth = lottieHandler_->GetLottieSize().width;
    int componentHeight = lottieHandler_->GetLottieSize().height;

    if (animWidth == 0 || animHeight == 0) {
        DRAWING_LOGE("LottieXcomponentNode::UpdateContentMode Number is 0");
        InitializePosition(componentWidth, componentHeight);
        lottieHandler_->dataManager_->InitRect(position_.width, position_.height);
        return;
    }
    // 准备参数
    ContentModeParams params;
    params.animWidth = animWidth;
    params.animHeight = animHeight;
    params.componentWidth = componentWidth;
    params.componentHeight = componentHeight;
    params.animAspectRatio = static_cast<float>(animWidth) / animHeight;
    params.componentAspectRatio = static_cast<float>(componentWidth) / componentHeight;

    // 初始化位置
    InitializePosition(componentWidth, componentHeight);

    // 根据contentMode设置缩放和对齐方式
    if (contentMode == "Contain") {
        position_.keepAspectRatio = true;
    } else if (contentMode == "Fill") {
        position_.keepAspectRatio = false;
    } else if (contentMode == "Cover") {
        ApplyCoverMode(params);
    } else if (contentMode == "Top" || contentMode == "Bottom") {
        ApplyTopBottomMode(contentMode, params);
    } else if (contentMode == "ContainCrop") {
        ApplyContainCropMode(params);
    } else {
        position_.keepAspectRatio = true;
    }
    
    lottieHandler_->dataManager_->InitRect(position_.width, position_.height);
}

void LottieXcomponentNode::RenderOnSurfaceChanged(OH_NativeXComponent *component, void *window, bool isOnSurfaceCreated)
{
    uint64_t width;
    uint64_t height;
    char idStr[OH_XCOMPONENT_ID_LEN_MAX + NUM_1] = {};
    uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + NUM_1;
    int32_t ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize);
    if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
        DRAWING_LOGE("LottieXcomponentNode::RenderOnSurfaceChanged: Error GetXComponentId Failed");
        return;
    }
    ret = OH_NativeXComponent_GetXComponentSize(component, window, &width, &height);
    if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
        DRAWING_LOGE("LottieXcomponentNode::RenderOnSurfaceChanged: Error GetXComponentSize Failed");
        return;
    }
    DRAWING_LOGD("RenderOnSurfaceChanged ret=%{public}d width=%{public}lu,"
                 "height=%{public}lu id=%{public}s",
                 ret, width, height, idStr);
    if (isOnSurfaceCreated) {
        LottieManagerMap::getInstance().SetWindowById(idStr, window);
        uint64_t usage = 0;
        int32_t ret = OH_NativeWindow_NativeWindowHandleOpt((OHNativeWindow *)window, GET_USAGE, &usage);
        if (ret != NATIVE_ERROR_OK) {
            DRAWING_LOGE("LottieXcomponentNode::OH_NativeWindow_NativeWindowHandleOpt: Error %{public}d", ret);
        }
        usage &= ~NATIVEBUFFER_USAGE_CPU_READ;
        int32_t ret2 = OH_NativeWindow_NativeWindowHandleOpt((OHNativeWindow *)window, SET_USAGE, usage);
        if (ret2 != NATIVE_ERROR_OK) {
            DRAWING_LOGE("LottieXcomponentNode::OH_NativeWindow_NativeWindowHandleOpt: Error %{public}d", ret2);
        }
    }
    LottieManagerMap::getInstance().SetRectById(idStr, width, height);
    auto lottieHandler = LottieManagerMap::getInstance().GetHandleById(idStr);
    if (lottieHandler != nullptr) {
        lottieHandler->SetLottieSize(width, height);
        if (lottieHandler->updateContentMode) {
            lottieHandler->updateContentMode();
        }
        if (isOnSurfaceCreated && lottieHandler->GetLottieConfig()->GetAutoplay()) {
            lottieHandler->Play();
        } else if (!isOnSurfaceCreated && lottieHandler->renderCurrentFrame && lottieHandler->GetIsPaused()) {
            lottieHandler->renderCurrentFrame();
        } else if (lottieHandler->renderAndAdvanceFrame) {
            lottieHandler->renderAndAdvanceFrame();
        }
    } else {
        DRAWING_LOGW("LottieXcomponentNode::RenderOnSurfaceChanged: Warning LottieHandler not find");
    }
}

void LottieXcomponentNode::OnSurfaceCreated(OH_NativeXComponent *component, void *window)
{
    DRAWING_LOGD("OnSurfaceCreated");
    RenderOnSurfaceChanged(component, window, true);
}

void LottieXcomponentNode::OnSurfaceChanged(OH_NativeXComponent *component, void *window)
{
    DRAWING_LOGD("OnSurfaceChanged");
    RenderOnSurfaceChanged(component, window, false);
}

void LottieXcomponentNode::OnDispatchTouchEvent(OH_NativeXComponent *component, void *window)
{
    DRAWING_LOGD("OnDispatchTouchEvent");
}

void LottieXcomponentNode::OnSurfaceDestroyed(OH_NativeXComponent *component, void *window)
{
    char idStr[OH_XCOMPONENT_ID_LEN_MAX + NUM_1] = {};
    uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + NUM_1;
    int32_t ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize);
    if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
        DRAWING_LOGE("LottieXcomponentNode::RenderOnSurfaceChanged: Error GetXComponentId Failed");
        return;
    }
    PopCallback(idStr);
    DRAWING_LOGD("OnSurfaceDestroyed lottieId(%{public}s)", idStr);
}

}